Trie前缀树——数据结构系列教程(c++版)

梦想不会自己发光,真正闪耀的是那个为梦狂奔的你。献给知行的孩子们!(Eric.He著)


  Trie(字典树/前缀树)是计算机科学中的一种特殊多叉树数据结构,核心优势是能高效处理字符串的插入、查询(完整单词/前缀)操作,时间复杂度仅与字符串长度相关(O(L))。Trie树通过共享字符串前缀节省存储空间,在自动补全、拼写检查、词典检索、IP路由等场景中广泛应用。本教程基于C++实现完整的Trie树,包含插入、查询单词、查询前缀、删除单词等核心功能,从核心原理、代码结构到实战使用,全面讲解Trie树的原理与实现。

教程目录导航

一、Trie树的核心原理

1.1 Trie树的性质

  1. 每个节点代表一个字符,节点本身不存储完整单词,仅存储路径上的字符组合;
  2. 从根节点到某一节点的路径拼接形成字符串(前缀/完整单词);
  3. 每个节点包含一个结束标记(is_end),标识该节点是否是某个单词的结尾;
  4. 具有相同前缀的单词共享前缀路径,大幅节省存储空间;
  5. 通常为26叉树(对应小写字母),也可扩展为任意字符集。

演示动画

1.2 核心优势

  1. 高效的字符串操作:
    • 插入/查询时间复杂度:O(L)(L为字符串长度),与单词数量无关;
    • 前缀匹配:哈希表无法高效实现的场景,Trie树是最优解(如自动补全);
    • 空间效率:共享前缀,比存储所有完整字符串更节省空间。
  2. 典型应用场景:
    • 搜索引擎自动补全、输入法联想;
    • 拼写检查、词典检索;
    • IP路由表最长前缀匹配;
    • 词频统计、字符串排序。
  3. 扩展性强:可通过节点扩展存储额外信息(如词频、权重、计数等)。

1.3 关键特征

  1. 字符索引计算:idx = c - 'a'(小写字母,对应0-25);
  2. 根节点:空节点,不存储任何字符,是所有单词的起始点;
  3. 结束标记:is_end 标识路径是否构成完整单词;
  4. 子节点:每个节点包含26个指针(对应a-z),初始化为nullptr。

1.4 核心操作逻辑

Trie树的核心操作围绕「路径遍历」展开,所有操作均从根节点开始,按字符逐个遍历子节点:

(1)插入逻辑

从根节点开始,遍历单词的每个字符:

  1. 计算字符对应的索引(c - 'a');
  2. 若对应子节点不存在,创建新节点;
  3. 移动到子节点,继续处理下一个字符;
  4. 遍历结束后,标记最后一个节点的is_end为true(标识完整单词)。

(2)查询逻辑

从根节点开始,遍历单词的每个字符:

  1. 计算字符对应的索引(c - 'a');
  2. 若对应子节点不存在,说明单词/前缀不存在;
  3. 移动到子节点,继续处理下一个字符;
  4. 遍历结束后:
    • 查询单词:需检查最后一个节点的is_end是否为true;
    • 查询前缀:只需路径存在即可,无需检查is_end。

(3)删除逻辑

递归遍历单词路径,从叶子节点回溯:

  1. 找到单词的最后一个节点,取消is_end标记;
  2. 回溯判断节点是否可删除(无is_end标记 + 无任何子节点);
  3. 若可删除,释放节点内存并置空父节点对应指针;
  4. 继续回溯,直到根节点或遇到不可删除的节点。

二、代码结构解析

2.1 整体架构

提供的代码是完整的Trie树实现,采用C++结构体封装,分为头文件(声明)和源文件(实现)两部分,核心结构如下:

模块 作用 关键结构/函数
Trie节点结构体 封装节点属性 TrieNode(子节点数组、is_end标记)
Trie树类 封装所有操作 Trie(根节点、插入/查询/删除等方法)

2.2 关键结构体详解

(1)Trie节点结构体(TrieNode)


// Trie节点结构体
struct TrieNode {
    TrieNode* children[26];  // 子节点数组:26个元素,对应a-z
    bool is_end;             // 标记是否是单词结尾
    int count;               // 扩展:记录单词出现次数(可选)

    // 构造函数:初始化节点
    TrieNode() : is_end(false), count(0) {
        for (int i = 0; i < 26; ++i) {
            children[i] = nullptr; // 所有子节点初始化为空
        }
    }

    // 析构函数:递归释放子节点内存
    ~TrieNode() {
        for (int i = 0; i < 26; ++i) {
            if (children[i] != nullptr) {
                delete children[i];
                children[i] = nullptr;
            }
        }
    }
};

(2)Trie树类(Trie)


// Trie树类
class Trie {
private:
    TrieNode* root; // 根节点(空节点)

    // 递归删除辅助函数
    bool removeHelper(TrieNode* node, const string& word, int idx);

public:
    // 构造函数:初始化根节点
    Trie() {
        root = new TrieNode();
    }

    // 析构函数:释放根节点(递归释放所有子节点)
    ~Trie() {
        delete root;
        root = nullptr;
    }

    // 核心操作
    void insert(const string& word);       // 插入单词
    bool search(const string& word);       // 查询单词是否存在
    bool startsWith(const string& prefix); // 查询前缀是否存在
    bool remove(const string& word);       // 删除单词
    int getCount(const string& word);      // 获取单词出现次数(扩展)
    void printAllWords();                  // 打印所有单词(扩展)
    void printAllWordsHelper(TrieNode* node, string& path); // 辅助打印
};

三、核心操作实现详解

3.1 插入(insert)

插入流程:

  1. 从根节点开始,遍历单词的每个字符;
  2. 计算字符索引(c - 'a'),若子节点不存在则创建;
  3. 移动到子节点,继续处理下一个字符;
  4. 遍历结束后,标记is_end为true,词频count+1。
void Trie::insert(const string& word) {
    TrieNode* curr = root;
    for (char c : word) {
        int idx = c - 'a'; // 字符转索引(a->0, b->1...z->25)
        if (curr->children[idx] == nullptr) {
            curr->children[idx] = new TrieNode(); // 创建新节点
        }
        curr = curr->children[idx]; // 移动到子节点
    }
    curr->is_end = true; // 标记单词结束
    curr->count++;       // 词频+1
}

3.2 查询单词(search)

查询流程:

  1. 从根节点开始,遍历单词的每个字符;
  2. 若某一步子节点不存在,返回false;
  3. 遍历结束后,返回最后一个节点的is_end(必须是完整单词)。
bool Trie::search(const string& word) {
    TrieNode* curr = root;
    for (char c : word) {
        int idx = c - 'a';
        if (curr->children[idx] == nullptr) {
            return false; // 路径中断,单词不存在
        }
        curr = curr->children[idx];
    }
    return curr->is_end; // 必须是完整单词
}

3.3 查询前缀(startsWith)

查询流程:

  1. 逻辑与search类似,但无需检查is_end;
  2. 只要前缀路径存在,即返回true。
bool Trie::startsWith(const string& prefix) {
    TrieNode* curr = root;
    for (char c : prefix) {
        int idx = c - 'a';
        if (curr->children[idx] == nullptr) {
            return false; // 前缀路径不存在
        }
        curr = curr->children[idx];
    }
    return true; // 前缀存在即可,无需检查是否是完整单词
}

3.4 删除单词(remove)

删除流程(递归回溯):

  1. 递归遍历到单词最后一个字符;
  2. 取消is_end标记,词频count-1;
  3. 回溯判断节点是否可删除(无is_end + 无任何子节点);
  4. 可删除则释放节点并置空父节点指针,否则保留。
// 递归删除辅助函数
bool Trie::removeHelper(TrieNode* node, const string& word, int idx) {
    // 递归终止:处理到单词最后一个字符
    if (idx == word.size()) {
        // 不是单词结尾,说明单词不存在
        if (!node->is_end) return false;
        // 取消结尾标记,词频-1
        node->is_end = false;
        node->count--;
        // 判断该节点是否可以删除(无任何子节点)
        for (int i = 0; i < 26; ++i) {
            if (node->children[i] != nullptr) return false;
        }
        return true;
    }

    int c_idx = word[idx] - 'a';
    // 路径不存在,单词不存在
    if (node->children[c_idx] == nullptr) return false;

    // 递归删除子节点
    bool can_delete = removeHelper(node->children[c_idx], word, idx + 1);

    // 如果子节点可以删除,释放内存并置空
    if (can_delete) {
        delete node->children[c_idx];
        node->children[c_idx] = nullptr;

        // 判断当前节点是否可以删除(无结尾标记 + 无其他子节点)
        if (!node->is_end) {
            for (int i = 0; i < 26; ++i) {
                if (node->children[i] != nullptr) return false;
            }
            return true;
        }
    }

    return false;
}

// 对外删除接口
bool Trie::remove(const string& word) {
    return removeHelper(root, word, 0);
}

四、实战使用示例

4.1 基础字符串使用示例

#include "Trie.h"
#include <iostream>
#include <string>

int main() {   
    Trie trie;

    // 1. 插入单词
    trie.insert("apple");
    trie.insert("app");
    trie.insert("banana");
    trie.insert("band");
    trie.insert("apple"); // 重复插入,词频+1

    // 2. 查询单词
    cout << "查询 \"apple\": " << (trie.search("apple") ? "存在" : "不存在") << endl;    // 存在
    cout << "查询 \"app\": " << (trie.search("app") ? "存在" : "不存在") << endl;      // 存在
    cout << "查询 \"appl\": " << (trie.search("appl") ? "存在" : "不存在") << endl;    // 不存在
    cout << "apple 出现次数: " << trie.getCount("apple") << endl; // 2

    // 3. 查询前缀
    cout << "查询前缀 \"app\": " << (trie.startsWith("app") ? "存在" : "不存在") << endl;  // 存在
    cout << "查询前缀 \"ban\": " << (trie.startsWith("ban") ? "存在" : "不存在") << endl;  // 存在
    cout << "查询前缀 \"xyz\": " << (trie.startsWith("xyz") ? "存在" : "不存在") << endl;  // 不存在

    // 4. 打印所有单词
    cout << "所有单词: ";
    trie.printAllWords(); // 输出: app apple banana band

    // 5. 删除单词
    cout << "删除 \"app\": " << (trie.remove("app") ? "成功" : "失败") << endl;        // 成功
    cout << "删除后查询 \"app\": " << (trie.search("app") ? "存在" : "不存在") << endl; // 不存在
    cout << "删除后查询前缀 \"app\": " << (trie.startsWith("app") ? "存在" : "不存在") << endl; // 存在(apple还在)

    return 0;
}

输出结果


查询 "apple": 存在
查询 "app": 存在
查询 "appl": 不存在
apple 出现次数: 2
查询前缀 "app": 存在
查询前缀 "ban": 存在
查询前缀 "xyz": 不存在
所有单词: app apple banana band 
删除 "app": 成功
删除后查询 "app": 不存在
删除后查询前缀 "app": 存在
            

4.2 自定义类型(带权重)使用示例

扩展TrieNode支持权重存储,实现带权重的单词插入/查询:

// 扩展TrieNode结构体(Entitys.h)
struct WeightedTrieNode {
    WeightedTrieNode* children[26];
    bool is_end;
    int weight; // 单词权重

    WeightedTrieNode() : is_end(false), weight(0) {
        for (int i = 0; i < 26; ++i) {
            children[i] = nullptr;
        }
    }

    ~WeightedTrieNode() {
        for (int i = 0; i < 26; ++i) {
            if (children[i] != nullptr) {
                delete children[i];
            }
        }
    }
};

// 带权重的Trie类(Trie.h)
class WeightedTrie {
private:
    WeightedTrieNode* root;

public:
    WeightedTrie() { root = new WeightedTrieNode(); }
    ~WeightedTrie() { delete root; }

    // 插入单词并设置权重
    void insert(const string& word, int weight) {
        WeightedTrieNode* curr = root;
        for (char c : word) {
            int idx = c - 'a';
            if (curr->children[idx] == nullptr) {
                curr->children[idx] = new WeightedTrieNode();
            }
            curr = curr->children[idx];
        }
        curr->is_end = true;
        curr->weight = weight;
    }

    // 查询单词权重
    int getWeight(const string& word) {
        WeightedTrieNode* curr = root;
        for (char c : word) {
            int idx = c - 'a';
            if (curr->children[idx] == nullptr) {
                return -1; // 单词不存在
            }
            curr = curr->children[idx];
        }
        return curr->is_end ? curr->weight : -1;
    }
};

// 使用示例
int main() {
    WeightedTrie wt;
    wt.insert("apple", 10);
    wt.insert("banana", 5);

    cout << "apple 权重: " << wt.getWeight("apple") << endl;  // 10
    cout << "banana 权重: " << wt.getWeight("banana") << endl;// 5
    cout << "orange 权重: " << wt.getWeight("orange") << endl;// -1

    return 0;
}

输出结果


apple 权重: 10
banana 权重: 5
orange 权重: -1
            

五、完整可运行代码

5.1 Entitys.h 头文件


#ifndef ENTITYS_H_INCLUDED
#define ENTITYS_H_INCLUDED

#include <string>
#include <vector>

//************************************************************************************************************************************************************************
//                                                                      自定义类型
//************************************************************************************************************************************************************************

//========================================================================================================================================================================
//                                                                    学生结构体(Student)
//========================================================================================================================================================================
struct Student {
    int id;// 学号
    std::string name;// 姓名
    std::string dob;// 出生日期
    std::string sex;// 性别
    std::string gender;// 民族
    std::string address;// 地址
    float score;// 入学总分
    std::string school;// 学校
    std::string team;// 班级
    std::string status;// 状态

    bool operator<(const Student& other) const { return id < other.id; }
    bool operator>(const Student& other) const { return id > other.id; }
    bool operator==(const Student& other) const { return id == other.id; }
    bool operator!=(const Student& other) const { return id != other.id; }

    friend std::ostream& operator<<(std::ostream& os, const Student& s) {
        os << "[" << s.id<< ", " << s.name << ", " << s.dob << ", " << s.sex << ", " << s.gender  << ", " << s.address << ", " << s.score<< ", " << s.school<< ", " << s.team<< ", " << s.status << "]";
        return os;
    }
};

#endif // ENTITYS_H_INCLUDED
                    

5.2 Trie.h 头文件


#ifndef TRIE_H_INCLUDED
#define TRIE_H_INCLUDED

#include <iostream>
#include <string>
#include <vector>

//========================================================================================================================================================================
//                                                                    Trie节点结构体(TrieNode)
//========================================================================================================================================================================
struct TrieNode {
    TrieNode* children[26];  // 子节点数组:26个元素,对应a-z
    bool is_end;             // 标记是否是单词结尾
    int count;               // 单词出现次数

    // 构造函数
    TrieNode() : is_end(false), count(0) {
        for (int i = 0; i < 26; ++i) {
            children[i] = nullptr;
        }
    }

    // 析构函数:递归释放子节点
    ~TrieNode() {
        for (int i = 0; i < 26; ++i) {
            if (children[i] != nullptr) {
                delete children[i];
                children[i] = nullptr;
            }
        }
    }
};

//========================================================================================================================================================================
//                                                                    带权重Trie节点结构体(WeightedTrieNode)
//========================================================================================================================================================================
struct WeightedTrieNode {
    WeightedTrieNode* children[26];
    bool is_end;
    int weight; // 单词权重

    WeightedTrieNode() : is_end(false), weight(0) {
        for (int i = 0; i < 26; ++i) {
            children[i] = nullptr;
        }
    }

    ~WeightedTrieNode() {
        for (int i = 0; i < 26; ++i) {
            if (children[i] != nullptr) {
                delete children[i];
            }
        }
    }
};

//========================================================================================================================================================================
//
//                                                                 Trie树类(Trie)
//
//========================================================================================================================================================================
class Trie {
private:
    TrieNode* root; // 根节点

    // 递归删除辅助函数
    bool removeHelper(TrieNode* node, const std::string& word, int idx);
    // 打印所有单词辅助函数
    void printAllWordsHelper(TrieNode* node, std::string& path);

public:
    // 构造函数
    Trie();

    // 析构函数
    ~Trie();

    // 核心操作
    void insert(const std::string& word);       // 插入单词
    bool search(const std::string& word);       // 查询单词是否存在
    bool startsWith(const std::string& prefix); // 查询前缀是否存在
    bool remove(const std::string& word);       // 删除单词
    int getCount(const std::string& word);      // 获取单词出现次数
    void printAllWords();                       // 打印所有单词
};

//========================================================================================================================================================================
//
//                                                                 带权重Trie树类(WeightedTrie)
//
//========================================================================================================================================================================
class WeightedTrie {
private:
    WeightedTrieNode* root;

public:
    // 构造函数
    WeightedTrie();

    // 析构函数
    ~WeightedTrie();

    // 插入单词并设置权重
    void insert(const std::string& word, int weight);

    // 查询单词权重
    int getWeight(const std::string& word);

    // 查询单词是否存在
    bool search(const std::string& word);
};

#endif // TRIE_H_INCLUDED

5.3 Trie.cpp 程序代码


#include "Trie.h"

//========================================================================================================================================================================
//
//                                                                 Trie树类实现
//
//========================================================================================================================================================================

// 构造函数
Trie::Trie() {
    root = new TrieNode();
}

// 析构函数
Trie::~Trie() {
    delete root;
    root = nullptr;
}

// 插入单词
void Trie::insert(const std::string& word) {
    TrieNode* curr = root;
    for (char c : word) {
        int idx = c - 'a';
        if (curr->children[idx] == nullptr) {
            curr->children[idx] = new TrieNode();
        }
        curr = curr->children[idx];
    }
    curr->is_end = true;
    curr->count++;
}

// 查询单词是否存在
bool Trie::search(const std::string& word) {
    TrieNode* curr = root;
    for (char c : word) {
        int idx = c - 'a';
        if (curr->children[idx] == nullptr) {
            return false;
        }
        curr = curr->children[idx];
    }
    return curr->is_end;
}

// 查询前缀是否存在
bool Trie::startsWith(const std::string& prefix) {
    TrieNode* curr = root;
    for (char c : prefix) {
        int idx = c - 'a';
        if (curr->children[idx] == nullptr) {
            return false;
        }
        curr = curr->children[idx];
    }
    return true;
}

// 递归删除辅助函数
bool Trie::removeHelper(TrieNode* node, const std::string& word, int idx) {
    if (idx == word.size()) {
        if (!node->is_end) return false;
        node->is_end = false;
        node->count--;
        for (int i = 0; i < 26; ++i) {
            if (node->children[i] != nullptr) return false;
        }
        return true;
    }

    int c_idx = word[idx] - 'a';
    if (node->children[c_idx] == nullptr) return false;

    bool can_delete = removeHelper(node->children[c_idx], word, idx + 1);

    if (can_delete) {
        delete node->children[c_idx];
        node->children[c_idx] = nullptr;

        if (!node->is_end) {
            for (int i = 0; i < 26; ++i) {
                if (node->children[i] != nullptr) return false;
            }
            return true;
        }
    }

    return false;
}

// 删除单词
bool Trie::remove(const std::string& word) {
    return removeHelper(root, word, 0);
}

// 获取单词出现次数
int Trie::getCount(const std::string& word) {
    TrieNode* curr = root;
    for (char c : word) {
        int idx = c - 'a';
        if (curr->children[idx] == nullptr) {
            return 0;
        }
        curr = curr->children[idx];
    }
    return curr->is_end ? curr->count : 0;
}

// 打印所有单词辅助函数
void Trie::printAllWordsHelper(TrieNode* node, std::string& path) {
    if (node->is_end) {
        std::cout << path << " ";
    }

    for (int i = 0; i < 26; ++i) {
        if (node->children[i] != nullptr) {
            path.push_back('a' + i);
            printAllWordsHelper(node->children[i], path);
            path.pop_back();
        }
    }
}

// 打印所有单词
void Trie::printAllWords() {
    std::string path;
    printAllWordsHelper(root, path);
    std::cout << std::endl;
}

//========================================================================================================================================================================
//
//                                                                 带权重Trie树类实现
//
//========================================================================================================================================================================

// 构造函数
WeightedTrie::WeightedTrie() {
    root = new WeightedTrieNode();
}

// 析构函数
WeightedTrie::~WeightedTrie() {
    delete root;
}

// 插入单词并设置权重
void WeightedTrie::insert(const std::string& word, int weight) {
    WeightedTrieNode* curr = root;
    for (char c : word) {
        int idx = c - 'a';
        if (curr->children[idx] == nullptr) {
            curr->children[idx] = new WeightedTrieNode();
        }
        curr = curr->children[idx];
    }
    curr->is_end = true;
    curr->weight = weight;
}

// 查询单词权重
int WeightedTrie::getWeight(const std::string& word) {
    WeightedTrieNode* curr = root;
    for (char c : word) {
        int idx = c - 'a';
        if (curr->children[idx] == nullptr) {
            return -1;
        }
        curr = curr->children[idx];
    }
    return curr->is_end ? curr->weight : -1;
}

// 查询单词是否存在
bool WeightedTrie::search(const std::string& word) {
    WeightedTrieNode* curr = root;
    for (char c : word) {
        int idx = c - 'a';
        if (curr->children[idx] == nullptr) {
            return false;
        }
        curr = curr->children[idx];
    }
    return curr->is_end;
}

5.4 main.cpp 测试代码


#include <iostream>
#include <string>
#include "Trie.h"

int main() {   
    // 基础Trie树测试
    Trie trie;

    // 1. 插入单词
    trie.insert("apple");
    trie.insert("app");
    trie.insert("banana");
    trie.insert("band");
    trie.insert("apple"); // 重复插入

    // 2. 查询单词
    std::cout << "查询 \"apple\": " << (trie.search("apple") ? "存在" : "不存在") << std::endl;
    std::cout << "查询 \"app\": " << (trie.search("app") ? "存在" : "不存在") << std::endl;
    std::cout << "查询 \"appl\": " << (trie.search("appl") ? "存在" : "不存在") << std::endl;
    std::cout << "apple 出现次数: " << trie.getCount("apple") << std::endl;

    // 3. 查询前缀
    std::cout << "查询前缀 \"app\": " << (trie.startsWith("app") ? "存在" : "不存在") << std::endl;
    std::cout << "查询前缀 \"ban\": " << (trie.startsWith("ban") ? "存在" : "不存在") << std::endl;
    std::cout << "查询前缀 \"xyz\": " << (trie.startsWith("xyz") ? "存在" : "不存在") << std::endl;

    // 4. 打印所有单词
    std::cout << "所有单词: ";
    trie.printAllWords();

    // 5. 删除单词
    std::cout << "删除 \"app\": " << (trie.remove("app") ? "成功" : "失败") << std::endl;
    std::cout << "删除后查询 \"app\": " << (trie.search("app") ? "存在" : "不存在") << std::endl;
    std::cout << "删除后查询前缀 \"app\": " << (trie.startsWith("app") ? "存在" : "不存在") << std::endl;

    // 带权重Trie树测试
    WeightedTrie wt;
    wt.insert("apple", 10);
    wt.insert("banana", 5);
    std::cout << "\n带权重Trie测试:" << std::endl;
    std::cout << "apple 权重: " << wt.getWeight("apple") << std::endl;
    std::cout << "banana 权重: " << wt.getWeight("banana") << std::endl;
    std::cout << "orange 权重: " << wt.getWeight("orange") << std::endl;
    
    return 0;
}

输出结果


查询 "apple": 存在
查询 "app": 存在
查询 "appl": 不存在
apple 出现次数: 2
查询前缀 "app": 存在
查询前缀 "ban": 存在
查询前缀 "xyz": 不存在
所有单词: app apple banana band 
删除 "app": 成功
删除后查询 "app": 不存在
删除后查询前缀 "app": 存在

带权重Trie测试:
apple 权重: 10
banana 权重: 5
orange 权重: -1
    

六、注意事项

字符集约束

  • 默认实现仅支持小写字母(a-z),如需支持大写/数字/特殊字符,需调整索引计算逻辑;
  • 扩展字符集时,可将子节点数组替换为std::unordered_map<char, TrieNode*>,适配任意字符。

内存管理

  • TrieNode析构函数递归释放子节点,避免内存泄漏;
  • 删除操作需谨慎,仅释放无共享前缀的节点,避免误删其他单词的路径。

性能优化

  • 大规模数据场景下,可使用数组代替指针(减少内存碎片);
  • 预分配节点内存池,提升插入效率。

七、总结

Trie树是专为字符串前缀匹配设计的高效数据结构,核心是共享前缀路径字符索引遍历。其插入/查询操作的时间复杂度仅与字符串长度相关,是哈希表、红黑树等结构无法替代的最优解。

常见坑点与避坑经验

本教程通过完整的代码实现和实战示例,覆盖了Trie树的核心原理、关键操作和扩展功能。掌握Trie树的设计思想,能高效解决字符串前缀匹配、自动补全、词典检索等实际问题,是算法设计和工程开发中的重要工具。


返回顶部